package com.rm.zeyou.util.compute;


import com.rm.zeyou.exception.ErrorCodeException;
import com.rm.zeyou.exception.ErrorCodeExceptionEnum;

import java.math.BigDecimal;

import static java.math.BigDecimal.ROUND_DOWN;
import static java.math.BigDecimal.ROUND_HALF_UP;

/**
 * @Author ZhangKeChen
 * @Date 2022/9/23
 * @Description 金额计算工具类
 */

public class ArithmeticUtil<T> {

    /**
     * 默认四舍五入规则
     * 不四舍五入 ROUND_DOWN;
     * 四舍五入 ROUND_HALF_UP;
     */
    private static final int isRounding = ROUND_DOWN;
    private static final int Rounding = ROUND_HALF_UP;


    /**=================================================================================加法开始=================================================================================**/
    /**
     * 提供精确的减法运算
     *
     * @param numberOne 被加数
     * @param numberTwo 加数
     * @return 两个参数的和
     */
    public static <T> T bigDecimalAdd(T numberOne, T numberTwo) {
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T) a.add(b);
    }


    /**
     * 提供精确的减法运算,携带保留位数,四舍五入
     *
     * @param numberOne 被加数
     * @param numberTwo 加数
     * @param scale     保留scale 位小数
     * @return 两个参数的和
     */
    public static <T> T stringAdd(T numberOne, T numberTwo, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);

        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T)a.add(b).setScale(scale, isRounding);
    }


    /**
     * 提供精确的减法运算,携带保留位数,指定是否四舍五入
     *
     * @param numberOne  被加数
     * @param numberTwo  加数
     * @param scale      保留scale 位小数
     * @param isRounding 是否四舍五入
     * @return 两个参数的和
     */
    public static <T> T bigDecimalAdd(T numberOne, T numberTwo, int scale, boolean isRounding) {

        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.add(b).setScale(scale, resultIsRounding);
    }


    /**=================================================================================减法开始=================================================================================**/

    /**
     * 提供精确的减法运算。
     *
     * @param numberOne 被减数
     * @param numberTwo 减数
     * @return 两个参数的差
     */
    public static <T> T sub(T numberOne, T numberTwo) {
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T) a.subtract(b);
    }

    /**
     * 提供精确的减法运算,携带保留位数,四舍五入
     *
     * @param numberOne 被减数
     * @param numberTwo 减数
     * @param scale     保留scale 位小数
     * @return 两个参数的差
     */
    public static <T> T sub(T numberOne, T numberTwo, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }

        return (T)a.subtract(b).setScale(scale, isRounding);
    }

    /**
     * 提供精确的减法运算
     *
     * @param numberOne  被减数
     * @param numberTwo  减数
     * @param scale      保留scale 位小数
     * @param isRounding 是否四舍五入
     * @return 两个参数的差
     */
    public static <T> T sub(T numberOne, T numberTwo, int scale, boolean isRounding) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.subtract(b).setScale(scale, resultIsRounding);
    }

    /**=================================================================================乘法开始=================================================================================**/

    /**
     * 提供精确的乘法运算
     *
     * @param numberOne 被乘数
     * @param numberTwo 乘数
     * @return 两个参数的积
     */
    public static <T> T mul(T numberOne, T numberTwo) {
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T) a.multiply(b);
    }


    /**
     * 提供精确的乘法运算,携带保留位数,四舍五入
     *
     * @param numberOne 被乘数
     * @param numberTwo 乘数
     * @param scale     保留scale 位小数
     * @return 两个参数的积
     */
    public static <T> T mul(T numberOne, T numberTwo, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T) a.multiply(b).setScale(scale, isRounding).toString();
    }

    /**
     * 提供精确的乘法运算,指定四舍五入
     *
     * @param numberOne  被乘数
     * @param numberTwo  乘数
     * @param scale      保留scale 位小数
     * @param isRounding 是否四舍五入
     * @return 两个参数的积
     */
    public static <T> T mul(T numberOne, T numberTwo, int scale, boolean isRounding) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.multiply(b).setScale(scale, resultIsRounding);
    }

    /**=================================================================================除法开始=================================================================================**/

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指 定精度，以后的数字四舍五入
     *
     * @param numberOne 被除数
     * @param numberTwo 除数
     * @param scale     表示需要精确到小数点以后几位
     * @return 两个参数的商
     */
    public static <T> T div(T numberOne, T numberTwo, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T) a.divide(b, scale, isRounding);
    }

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指定精度，以后的数字指定四舍五入
     *
     * @param numberOne  被除数
     * @param numberTwo  除数
     * @param scale      表示需要精确到小数点以后几位
     * @param isRounding 是否四舍五入
     * @return 两个参数的商
     */
    public static <T> T div(T numberOne, T numberTwo, int scale, boolean isRounding) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.divide(b, scale, resultIsRounding);
    }

    /**=================================================================================保留数字方法开始=================================================================================**/


    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param number 需要四舍五入的数字
     * @param scale  小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static <T> T round(T number, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal b = null;
        if (number instanceof String) {
            b = new BigDecimal((String) number);
        } else if (number instanceof BigDecimal) {
            b = (BigDecimal) number;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T)b.setScale(scale, isRounding);
    }

    /**
     * 提供精确的小数位处理,指定四舍五入
     *
     * @param number     需要四舍五入的数字
     * @param scale      小数点后保留几位
     * @param isRounding 是否四舍五入
     * @return 四舍五入后的结果
     */
    public static <T> T round(T number, int scale, boolean isRounding) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        if (number instanceof String) {
            a = new BigDecimal((String) number);
        } else if (number instanceof BigDecimal) {
            a = (BigDecimal) number;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.setScale(scale, resultIsRounding);
    }
    /**=================================================================================取余方法开始=================================================================================**/

    /**
     * 取余数，四舍五入处理
     *
     * @param numberOne 被除数
     * @param numberTwo 除数
     * @param scale     小数点后保留几位
     * @return 余数
     */
    public static <T> T remainder(T numberOne, T numberTwo, int scale) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T)a.remainder(b).setScale(scale, ROUND_HALF_UP);
    }

    /**
     * 取余数，指定四舍五入
     *
     * @param numberOne  被除数
     * @param numberTwo  除数
     * @param scale      小数点后保留几位
     * @param isRounding 是否四舍五入
     * @return 余数
     */
    public static <T> T remainder(T numberOne, T numberTwo, int scale, boolean isRounding) {
        if (scale < 0) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_PARAM_ERROR);
        }
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int resultIsRounding = 0;
        if (isRounding) {
            resultIsRounding = ROUND_HALF_UP;
        } else {
            resultIsRounding = ROUND_DOWN;
        }
        return (T)a.remainder(b).setScale(scale, resultIsRounding);
    }


    /**=================================================================================比较方法开始=================================================================================**/

    /**
     * 比较大小
     *
     * @param numberOne 被比较数
     * @param numberTwo 比较数
     * @return 如果numberOne 大于等于numberTwo 则 返回true 否则false
     */
    public static <T> boolean compare(T numberOne, T numberTwo) {
        BigDecimal a = null;
        BigDecimal b = null;
        if (numberOne instanceof String && numberTwo instanceof String) {
            a = new BigDecimal((String) numberOne);
            b = new BigDecimal((String) numberTwo);
        } else if (numberOne instanceof BigDecimal && numberTwo instanceof BigDecimal) {
            a = (BigDecimal) numberOne;
            b = (BigDecimal) numberTwo;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        int bj = a.compareTo(b);
        boolean res;
        if (bj >= 0)
            res = true;
        else
            res = false;
        return res;
    }

    /** =================================================================================比较方法开始=================================================================================**/
    /**
     * 去除小数点后面的0
     *
     * @param number
     * @param <T>
     * @return
     */
    public static <T> T removeInvalidZero(T number) {
        BigDecimal a = null;
        if (number instanceof String) {
            a = new BigDecimal((String) number);
        } else if (number instanceof BigDecimal) {
            a = (BigDecimal) number;
        } else {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.ALGORITHM_FORMAT_ERROR);
        }
        return (T)a.stripTrailingZeros().toPlainString();
    }


    /**
     *  判断 BigDecimal 是否为整数
     * @param bd
     * @return true 为整数
     */
    public static boolean isInteger(BigDecimal bd) {
        bd = bd.stripTrailingZeros(); // 去除尾部的零
        return bd.scale() <= 0; // 检查小数位数是否为非正数
    }

    /**
     * BigDecimal不使用科学计数法返回
     * @Date 2024 8月 02 17:56
     * @param bigDecimal
     * @return
     */
    public static String formatBigDecimal(BigDecimal bigDecimal) {
        if (bigDecimal == null) {
            return "0";
        }
        if (bigDecimal.compareTo(new BigDecimal("0")) == 0) {
            return "0";
        } else {
            return bigDecimal.toPlainString();
        }
    }
}
